/*
 * Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
 */

package play.libs.ws.ahc;

import akka.stream.javadsl.Source;
import akka.util.ByteString;
import com.fasterxml.jackson.databind.JsonNode;
import org.w3c.dom.Document;
import play.libs.ws.*;
import play.mvc.Http;

import java.io.File;
import java.io.InputStream;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;

/**
 * A Play WS request backed by AsyncHTTPClient implementation.
 */
public class AhcWSRequest implements WSRequest {
    private static WSBodyWritables writables = new WSBodyWritables() {};

    private final AhcWSClient client;
    private final StandaloneAhcWSRequest request;
    private final Function<StandaloneWSResponse, WSResponse> responseFunction = AhcWSResponse::new;
    private final Function<StandaloneWSRequest, WSRequest> converter = new Function<StandaloneWSRequest, WSRequest>() {
        public WSRequest apply(StandaloneWSRequest standaloneWSRequest) {
            final StandaloneAhcWSRequest plainAhcWSRequest = (StandaloneAhcWSRequest) standaloneWSRequest;
            return new AhcWSRequest(client, plainAhcWSRequest);
        }
    };

    AhcWSRequest(AhcWSClient client, StandaloneAhcWSRequest request) {
        this.client = client;
        this.request = request;
    }

    @Override
    public CompletionStage<WSResponse> get() {
        return request.get().thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> patch(BodyWritable body) {
        return request.patch(body).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> patch(String string) {
        return request.patch(writables.body(string)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> patch(JsonNode jsonNode) {
        return request.patch(writables.body(jsonNode)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> patch(Document doc) {
        return request.patch(writables.body(doc)).thenApply(responseFunction);
    }

    @Deprecated
    @Override
    public CompletionStage<WSResponse> patch(InputStream inputStream) {
        return request.patch(writables.body(() -> inputStream)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> patch(File file) {
        return request.patch(writables.body(file)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> patch(Source<? super Http.MultipartFormData.Part<Source<ByteString, ?>>, ?> bodyPartSource) {
        return request.patch(writables.multipartBody(bodyPartSource)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> post(BodyWritable body) {
        return request.post(body).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> post(String string) {
        return request.post(writables.body(string)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> post(JsonNode json) {
        return request.post(writables.body(json)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> post(Document doc) {
        return request.post(writables.body(doc)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> post(InputStream is) {
        return request.post(writables.body(() -> is)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> post(File file) {
        return request.post(writables.body(file)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> post(Source<? super Http.MultipartFormData.Part<Source<ByteString, ?>>, ?> bodyPartSource) {
        return request.post(writables.multipartBody(bodyPartSource)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> put(BodyWritable body) {
        return request.put(body).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> put(String string) {
        return request.put(writables.body(string)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> put(JsonNode json) {
        return request.put(writables.body(json)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> put(Document doc) {
        return request.put(writables.body(doc)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> put(InputStream is) {
        return request.put(writables.body(() -> is)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> put(File file) {
        return request.put(writables.body(file)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> put(Source<? super Http.MultipartFormData.Part<Source<ByteString, ?>>, ?> bodyPartSource) {
        return request.put(writables.multipartBody(bodyPartSource)).thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> delete() {
        return request.delete().thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> head() {
        return request.head().thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> options() {
        return request.options().thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> execute(String method) {
        return request.setMethod(method).execute().thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> execute() {
        return request.execute().thenApply(responseFunction);
    }

    @Override
    public CompletionStage<WSResponse> stream() {
        return request.stream().thenApply(responseFunction);
    }

    @Override
    public WSRequest setMethod(String method) {
        return converter.apply(request.setMethod(method));
    }

    @Override
    public WSRequest setBody(BodyWritable bodyWritable) {
        return converter.apply(request.setBody(bodyWritable));
    }

    @Override
    public WSRequest setBody(String string) {
        return converter.apply(request.setBody(writables.body(string)));
    }

    @Override
    public WSRequest setBody(JsonNode json) {
        return converter.apply(request.setBody(writables.body(json)));
    }

    @Deprecated
    @Override
    public WSRequest setBody(InputStream is) {
        return converter.apply(request.setBody(writables.body(() -> is)));
    }

    @Override
    public WSRequest setBody(File file) {
        return converter.apply(request.setBody(writables.body(file)));
    }

    @Override
    public <U> WSRequest setBody(Source<ByteString, U> source) {
        return converter.apply(request.setBody(writables.body(source)));
    }

    /**
     * @deprecated use addHeader(name, value)
     */
    @Deprecated
    @Override
    public WSRequest setHeader(String name, String value) {
        return converter.apply(request.addHeader(name, value));
    }

    @Override
    public WSRequest setHeaders(Map<String, List<String>> headers) {
        return converter.apply(request.setHeaders(headers));
    }

    @Override
    public WSRequest addHeader(String name, String value) {
        return converter.apply(request.addHeader(name, value));
    }

    @Override
    public WSRequest setQueryString(String query) {
        return converter.apply(request.setQueryString(query));
    }

    /**
     * @deprecated Use addQueryParameter
     */
    @Deprecated
    @Override
    public WSRequest setQueryParameter(String name, String value) {
        return converter.apply(request.addQueryParameter(name, value));
    }

    @Override
    public WSRequest addQueryParameter(String name, String value) {
        return converter.apply(request.addQueryParameter(name, value));
    }

    @Override
    public WSRequest setQueryString(Map<String, List<String>> params) {
        return converter.apply(request.setQueryString(params));
    }

    @Override
    public WSRequest addCookie(WSCookie cookie) {
        return converter.apply(request.addCookie(cookie));
    }

    @Override
    public WSRequest addCookie(Http.Cookie cookie) {
        return converter.apply(request.addCookie(asCookie(cookie)));
    }

    private WSCookie asCookie(Http.Cookie cookie) {
        return new DefaultWSCookie(cookie.name(), cookie.value(),
                cookie.domain(),
                cookie.path(),
                Optional.ofNullable(cookie.maxAge()).map(Integer::longValue).filter(f -> f > -1L).orElse(null),
                cookie.secure(),
                cookie.httpOnly());
    }

    @Override
    public WSRequest addCookies(WSCookie... cookies) {
        return converter.apply(request.addCookies(cookies));
    }

    @Override
    public WSRequest setCookies(List<WSCookie> cookies) {
        return converter.apply(request.setCookies(cookies));
    }

    @Override
    public WSRequest setAuth(String userInfo) {
        return converter.apply(request.setAuth(userInfo));
    }

    @Override
    public WSRequest setAuth(String username, String password) {
        return converter.apply(request.setAuth(username, password));
    }

    @Override
    public WSRequest setAuth(String username, String password, WSAuthScheme scheme) {
        return converter.apply(request.setAuth(username, password, scheme));
    }

    @Override
    public StandaloneWSRequest setAuth(WSAuthInfo authInfo) {
        return converter.apply(request.setAuth(authInfo));
    }

    @Override
    public WSRequest sign(WSSignatureCalculator calculator) {
        return converter.apply(request.sign(calculator));
    }

    @Override
    public WSRequest setFollowRedirects(boolean followRedirects) {
        return converter.apply(request.setFollowRedirects(followRedirects));
    }

    @Override
    public WSRequest setVirtualHost(String virtualHost) {
        return converter.apply(request.setVirtualHost(virtualHost));
    }

    /**
     * @deprecated Use {@link #setRequestTimeout(Duration timeout)}
     * @param timeout the request timeout in milliseconds. A value of -1 indicates an infinite request timeout.
     */
    @Deprecated
    @Override
    public WSRequest setRequestTimeout(long timeout) {
        Duration d;
        if (timeout == -1) {
            d = Duration.of(1, ChronoUnit.YEARS);
        } else {
            d = Duration.ofMillis(timeout);
        }
        return converter.apply(request.setRequestTimeout(d));
    }

    @Override
    public WSRequest setRequestTimeout(Duration timeout) {
        return converter.apply(request.setRequestTimeout(timeout));
    }

    @Override
    public WSRequest setRequestFilter(WSRequestFilter filter) {
        return converter.apply(request.setRequestFilter(filter));
    }

    @Override
    public WSRequest setContentType(String contentType) {
        return converter.apply(request.setContentType(contentType));
    }

    @Override
    public Optional<WSAuthInfo> getAuth() {
        return request.getAuth();
    }

    @Override
    public Optional<BodyWritable> getBody() {
        return request.getBody();
    }

    @Override
    public Optional<WSSignatureCalculator> getCalculator() {
        return request.getCalculator();
    }

    @Override
    public Optional<String> getContentType() {
        return request.getContentType();
    }

    @Override
    public Optional<Boolean> getFollowRedirects() {
        return request.getFollowRedirects();
    }

    @Override
    public String getUrl() {
        return request.getUrl();
    }

    @Override
    public Map<String, List<String>> getHeaders() {
        return request.getHeaders();
    }

    @Override
    public List<String> getHeaderValues(String name) {
        return request.getHeaderValues(name);
    }

    @Override
    public Optional<String> getHeader(String name) {
        return request.getHeader(name);
    }

    @Override
    public Optional<Duration> getRequestTimeout() {
        return request.getRequestTimeout();
    }

    @Override
    public Map<String, List<String>> getQueryParameters() {
        return request.getQueryParameters();
    }

}
